﻿//This C# code file was released under the Ms-PL license
//http://www.opensource.org/licenses/ms-pl.html
//This script was originally intended for use with Microsoft Dynamics AX
//and maintained and distributed as a project on CodePlex
//http://dynamicsaxbuild.codeplex.com

using System;
using System.Collections.Generic;
using System.Text;
using System.Activities;
using System.Diagnostics;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Build.Workflow.Activities;
using System.Xml;

namespace AXTFSBuildLibrary
{
    public enum ClientCommand
    {
        IMPORTXPO,
        IMPORTLABELFILE,
        IMPORTLABELFOLDER,
        SYNCHRONIZE,
        COMPILEXPP,
        GENERATECIL,
        RUNTESTPROJECT,
        XMLDOCUMENTATION,
        AUTORUN,
        STARTUPCMD
    }

    [BuildActivity(HostEnvironmentOption.All)]
    public sealed class ClientExecute : CodeActivity
    {
        [RequiredArgument]
        public InArgument<string> AXClientBinPath { get; set; }

        public InArgument<string> AXConfigurationFile { get; set; }

        public InArgument<string> ServerName { get; set; }
        public InArgument<UInt16> PortNumber { get; set; }
        public InArgument<string> Layer { get; set; }
        public InArgument<string> LayerCode { get; set; }
        public InArgument<string> ModelManifest { get; set; }
        public InArgument<string> ModelName { get; set; }
        public InArgument<string> ModelPublisher { get; set; }

        [RequiredArgument]
        public InArgument<UInt16> TimeOutMinutes { get; set; }

        //public InArgument<bool> LazyClassLoading { get; set; }
        //public InArgument<bool> LazyTableLoading { get; set; }

        [RequiredArgument]
        public InArgument<DynamicsAXVersion> Version { get; set; }

        [RequiredArgument]
        public InArgument<ClientCommand> Command { get; set; }
        public InArgument<string> CommandArgument { get; set; }

        protected override void Execute(CodeActivityContext context)
        {
            string modelName = ModelName.Get(context);
            string modelPublisher = ModelPublisher.Get(context);
            string configFile = AXConfigurationFile.Get(context);
            string serverName = ServerName.Get(context);
            string portNumber = PortNumber.Get(context).ToString();
            string layer = Layer.Get(context);
            string layerCode = Layer.Get(context);
            string commandArgument = CommandArgument.Get(context);
            List<string> parameterList = new List<string>();

            if (!String.IsNullOrEmpty(configFile))
            {
                parameterList.Add(String.Format("\"{0}\"", configFile));
            }
            else
            {
                // TODO: -AOS doesn't seem to be working!!
                if (!String.IsNullOrEmpty(serverName) && !String.IsNullOrEmpty(portNumber))
                    parameterList.Add(String.Format("-aos={0}@{1}", serverName, portNumber));
                else
                    context.TrackBuildWarning("No configuration file or server/port specified.");

                if (!String.IsNullOrEmpty(layer))
                {
                    if (String.IsNullOrEmpty(layerCode) && layer.ToLower() != "usr" && layer.ToLower() != "usp")
                        throw new Exception("Missing layer code for layer different than USR/USP");

                    parameterList.Add(String.Format("-aol={0}", layer));
                    parameterList.Add(String.Format("-aolcode={0}", layerCode));
                }
            }

            if (!String.IsNullOrEmpty(ModelManifest.Get(context)))
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(ModelManifest.Get(context));

                modelName = doc.DocumentElement.SelectSingleNode("Name").InnerText;
                modelPublisher = doc.DocumentElement.SelectSingleNode("Publisher").InnerText;
                string modelLayer = doc.DocumentElement.SelectSingleNode("Layer").InnerText;

                if (!String.IsNullOrEmpty(layer) && modelLayer.ToLower() != layer.ToLower())
                {
                    throw new Exception(String.Format("Trying to import into model {0} in layer {1}, but configuration file connects to layer {2}", modelName, modelLayer.ToLower(), layer.ToLower()));
                }
                // TODO: check against configuration file??!
            }

            if (!String.IsNullOrEmpty(modelName))
            {
                if (String.IsNullOrEmpty(modelPublisher) || String.IsNullOrEmpty(modelPublisher.Trim()))
                {
                    parameterList.Add(String.Format("\"-Model={0}\"", modelName));
                }
                else
                {
                    parameterList.Add(String.Format("\"-Model=({0},{1})\"", modelName, modelPublisher));
                }
            }

            parameterList.Add("-MINIMIZE");

            //if(LazyClassLoading.Get(context) == true)
            parameterList.Add("-LAZYCLASSLOADING");

            //if(LazyClassLoading.Get(context) == true)
            parameterList.Add("-LAZYTABLELOADING");

            string language;
            switch (Command.Get(context))
            {
                case ClientCommand.IMPORTXPO:
                    context.TrackBuildMessage("Executing import of XPO file");
                    parameterList.Add(String.Format("\"-AOTIMPORTFILE={0}\"", commandArgument));
                    parameterList.Add(String.Format("-NOCOMPILEONIMPORT"));

                    CallClient(parameterList, context);
                    break;
                case ClientCommand.IMPORTLABELFOLDER:
                    if (Version.Get(context) < DynamicsAXVersion.AX2012)
                        throw new Exception("The import labels command is only available in versions higher than AX 2009");

                    context.TrackBuildMessage("Executing label folder import");
                    parameterList.Add(String.Format("-NOCOMPILEONIMPORT"));

                    if (System.IO.Directory.Exists(commandArgument))
                    {
                        IEnumerable<string> files = System.IO.Directory.EnumerateFiles(commandArgument, String.Format("*.ald"));
                        foreach (string file in files)
                        {
                            language = file.Substring(file.LastIndexOf('\\') + 6, file.Length - 4 - file.LastIndexOf('\\') - 6);

                            if (!AxApplicationFiles.IsEmptyLabelFile(file))
                            {
                                context.TrackBuildMessage(String.Format("Importing labels for language {0}", language), BuildMessageImportance.High);

                                List<string> labelParameters = new List<string>();
                                labelParameters.AddRange(parameterList);
                                labelParameters.Add(String.Format("\"-StartupCmd=aldimport_{0}\"", file));

                                CallClient(labelParameters, context);
                            }
                            else
                            {
                                context.TrackBuildMessage(String.Format("Skipping label language {0} because it is empty", language));
                            }
                        }
                    }
                    else
                    {
                        context.TrackBuildMessage("Label files folder not found, skipping label import.");
                    }
                    break;
                case ClientCommand.IMPORTLABELFILE:
                    if (Version.Get(context) < DynamicsAXVersion.AX2012)
                        throw new Exception("The import labels command is only available in versions higher than AX 2009");

                    language = commandArgument.Substring(commandArgument.LastIndexOf('\\') + 6, commandArgument.Length - 4 - commandArgument.LastIndexOf('\\') + 6);

                    if (AxApplicationFiles.IsEmptyLabelFile(commandArgument))
                        context.TrackBuildMessage(String.Format("Skipping label file language {0} because it is empty", language));

                    context.TrackBuildMessage(String.Format("Executing label file import for language {0}", language), BuildMessageImportance.High);
                    parameterList.Add(String.Format("\"-StartupCmd=aldimport_{0}\"", commandArgument));
                    parameterList.Add(String.Format("-NOCOMPILEONIMPORT"));

                    CallClient(parameterList, context);
                    break;
                case ClientCommand.SYNCHRONIZE:
                    context.TrackBuildMessage("Executing data dictionary synchronize");
                    parameterList.Add("-StartupCmd=Synchronize");

                    CallClient(parameterList, context);
                    break;
                case ClientCommand.COMPILEXPP:
                    if (!String.IsNullOrEmpty(commandArgument))
                    {
                        context.TrackBuildMessage("Executing full X++ compile with cross-reference update");
                        parameterList.Add("-StartupCmd=CompileAll_+");
                    }
                    else
                    {
                        context.TrackBuildMessage("Executing full X++ compile");
                        parameterList.Add("-StartupCmd=CompileAll");
                    }

                    CallClient(parameterList, context);
                    break;
                case ClientCommand.GENERATECIL:
                    if (Version.Get(context) < DynamicsAXVersion.AX2012)
                        throw new Exception("The generate CIL command is only available in versions higher than AX 2009");
                    context.TrackBuildMessage("Executing CIL generation");
                    parameterList.Add("-StartupCmd=CompileIL");

                    CallClient(parameterList, context);
                    break;
                case ClientCommand.RUNTESTPROJECT:
                    context.TrackBuildMessage("Executing test project run");
                    if (String.IsNullOrEmpty(commandArgument))
                        throw new Exception("Expecting command argument containing the test project to run");
                    parameterList.Add(String.Format("-StartupCmd=RunTestProject_{0}", commandArgument));

                    CallClient(parameterList, context);
                    break;
                case ClientCommand.XMLDOCUMENTATION:
                    context.TrackBuildMessage("Executing XML documentation");
                    if (String.IsNullOrEmpty(commandArgument))
                        throw new Exception("Expecting command argument containing xml documentation filename");
                    parameterList.Add(String.Format("\"-StartupCmd=xmldocumentation_{0}\"", commandArgument));

                    CallClient(parameterList, context);
                    break;
                case ClientCommand.AUTORUN:
                    context.TrackBuildMessage(String.Format("Executing autorun script \"{0}\"", commandArgument));
                    parameterList.Add(String.Format("\"-StartupCmd=AutoRun_{0}\"", commandArgument));

                    CallClient(parameterList, context);
                    break;
                case ClientCommand.STARTUPCMD:
                    context.TrackBuildMessage(String.Format("Executing startup command \"{0}\"", commandArgument));
                    parameterList.Add(String.Format("\"-StartupCmd={0}\"", commandArgument));

                    CallClient(parameterList, context);
                    break;
            }
        }

        protected void CallClient(List<string> parameterList, CodeActivityContext context)
        {
            string parameterString = String.Join(" ", parameterList.ToArray());
            ProcessStartInfo processStartInfo = new ProcessStartInfo(AXClientBinPath.Get(context) + @"\Ax32.exe", parameterString);
            processStartInfo.WindowStyle = ProcessWindowStyle.Minimized;

            context.TrackBuildMessage(String.Format("Executing AX client with parameters \"{0}\"", parameterString));

            Process process = Process.Start(processStartInfo);

            if (!process.WaitForExit((int)new TimeSpan(0, TimeOutMinutes.Get(context), 0).TotalMilliseconds))   // Don't care about sub-millisecond precision here.
            {
                // Process is still running after the timeout has elapsed.
                try
                {
                    string text = String.Format("AX client execution of {0} timed out", Command.Get(context));
                    context.TrackBuildError(text);
                    process.Kill();
                }
                catch (InvalidOperationException)
                {
                    // Process does not exist or has already been killed.
                    // Don't throw the error just yet...
                    context.TrackBuildWarning("Could not kill client process because it is not running");
                }

                throw new System.TimeoutException(String.Format("Operation did not complete in {0} minutes.", TimeOutMinutes.Get(context)));
            }
        }
    }
}
